package com.bxl.netty.rpc.client.common.client;

import com.bxl.netty.rpc.common.Lifecycle;
import com.bxl.netty.rpc.common.client.Client;
import com.bxl.netty.rpc.common.context.Context;
import com.bxl.netty.rpc.client.common.handler.ClientHandlerAware;
import com.bxl.netty.rpc.common.util.HandlerFactory;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;


import java.net.InetSocketAddress;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * <>
 *
 * @author baixl
 * @date 2021/2/24
 */
public class ClientInfo extends Client implements Lifecycle {
    private volatile NioSocketChannel client;
    private String ip;
    private int port;
    private String app;
    private Context context;
    private Bootstrap bs;

    public String getIp() {
        return ip;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        ClientInfo that = (ClientInfo) o;

        if (port != that.port) return false;
        if (ip != null ? !ip.equals(that.ip) : that.ip != null) return false;
        return app != null ? app.equals(that.app) : that.app == null;
    }

    @Override
    public int hashCode() {
        int result = ip != null ? ip.hashCode() : 0;
        result = 31 * result + port;
        result = 31 * result + (app != null ? app.hashCode() : 0);
        return result;
    }

    public ClientInfo(String app, String ip, int port, Context clientContext) {
        this.app =app;
        this.ip = ip;
        this.port = port;
        this.context = clientContext;
    }

    public ClientInfo() {
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public NioSocketChannel getClient() {
        init();
        return client;
    }
    
    protected void init(){
        try {
            if (client == null) {
                synchronized (this) {
                    if (client == null) {
                        //暂时不考虑配置
                        client = create(this);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public String getApp() {
        return app;
    }

    public void setApp(String app) {
        this.app = app;
    }

    private NioSocketChannel create(final ClientInfo clientInfo) {
        //基于 netty 的客户端创建方式
        EventLoopGroup clientWorker = new NioEventLoopGroup(4);
        bs = new Bootstrap();
        ChannelFuture connect = bs.group(clientWorker)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ChannelPipeline p = ch.pipeline();

                        List<HandlerFactory> encoders = context.getEncoder();
                        if (encoders != null) {
                            for (HandlerFactory encoder : encoders) {
                                p.addLast((ChannelOutboundHandlerAdapter) encoder.getBean(context));
                            }
                        }
                        List<HandlerFactory> decoders = context.getDecoder();
                        if (decoders != null) {
                            for (HandlerFactory decoder : decoders) {
                                p.addLast((ChannelInboundHandlerAdapter) decoder.getBean(context));
                            }
                        }

                        List<HandlerFactory> clientHandlers = context.getHandler();
                        if (clientHandlers != null) {
                            for (HandlerFactory handler : clientHandlers) {
                                ChannelInboundHandlerAdapter bean = (ChannelInboundHandlerAdapter) handler.getBean(context);
                                if (bean instanceof ClientHandlerAware) {
                                    ClientHandlerAware clientHandler = (ClientHandlerAware) bean;
                                    clientHandler.setCliengInfo(clientInfo);
                                }

                                p.addLast(bean);
                            }
                        }


                    }
                }).connect(new InetSocketAddress(clientInfo.getIp(), clientInfo.getPort()));
        try {
            NioSocketChannel client = (NioSocketChannel) connect.sync().channel();
            //client.close();
            return client;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }


    public void doConnect() {
        if (client != null && client.isActive()) {
            return;
        }
        ChannelFuture future = bs.connect(new InetSocketAddress(ip, port));

        future.addListener(new ChannelFutureListener() {
            public void operationComplete(ChannelFuture futureListener) throws Exception {
                if (futureListener.isSuccess()) {
                    client = (NioSocketChannel) futureListener.channel();
                    System.out.println("Connect to server successfully!");
                } else {
                    System.out.println("Failed to connect to server, try connect after 10s");

                    futureListener.channel().eventLoop().schedule(new Runnable() {
                        public void run() {
                            System.out.println("重新连接");
                            doConnect();
                        }
                    }, 10, TimeUnit.SECONDS);
                }
            }
        });
    }

    public void start() {
        init();
    }

    public void stop() {
        if(client != null  && client.isActive()){
            client.disconnect();
            client.close();
        }
    }

    public void setContext(Context context) {
        this.context = context;
    }
}
